﻿#include "./view.h"
#include "./winview.h"
#include "layout/normallayout.h"

using namespace MiniUI;
using namespace MiniUI::Type;

const char* TypeObject<View>::s_szTypeName = "View";

View::View(const char* szType)
: UIObject(szType)
{
    Init();
}

View::View()
: UIObject(GetTypeName())
{
    Init();
}

View::~View()
{
    UIObject* pObj = GetFirstChild();
    while (pObj)
    {
        UIObject* pNext = pObj->m_pNext;
        pObj->m_pParent = nullptr;
        pObj->m_pPrev = nullptr;
        pObj->m_pNext = nullptr;
        pObj->Release();
        pObj = pNext;
    }

    if (m_pLayout)
    {
        m_pLayout->Release();
    }
}

void View::SetWinView(WinView* pWinView)
{
	m_pWinView = pWinView;
	//Set Child WinView
    UIObject* pObj = m_pChild;
    while (pObj)
    {
        pObj->SetWinView(pWinView);
        pObj = pObj->m_pNext;
    }
}

bool View::AddChild(UIObject* pChild)
{
    if (!pChild)
    {
        return false;
    }

    if (pChild->m_pParent == this)
    {
        return false;
    }

    UIObject* pObj = this;
    while (pObj)
    {
        if (pObj == pChild)
        {
            return false;
        }
        pObj = pObj->GetParent();
    }

    if (pChild->CheckType(View::GetTypeName()))
    {
        if (m_pWinView == pChild)
        {
            return false;
        }
    }

    AddObject(pChild);

    MiniRect rcChild;
    pChild->GetRect(rcChild);
    DoChange(rcChild);

    return true;
}

bool View::DeleteChild(UIObject* pChild)
{
    if (!pChild)
    {
        return false;
    }

    if (pChild->m_pParent != this)
    {
        return false;
    }

    DeleteObject(pChild);

    if (pChild == m_pObjMouseDown)
    {
        m_pObjMouseDown = nullptr;
    }

    if (pChild == m_pObjMouseEnter)
    {
        m_pObjMouseEnter = nullptr;
    }

    MiniRect rcChild;
    pChild->GetRect(rcChild);
    DoChange(rcChild);

    return true;
}

int View::InsertChild(int nPos, UIObject* pChild)
{
    if (!pChild || nPos < 0)
    {
        return -1;
    }

    if (pChild->m_pParent == this)
    {
        return -1;
    }

    UIObject* pObj = this;
    while (pObj)
    {
        if (pObj == pChild)
        {
            return -1;
        }
        pObj = pObj->GetParent();
    }

    if (pChild->CheckType(View::GetTypeName()))
    {
        if (m_pWinView == pChild)
        {
            return -1;
        }
    }

    int nIndex = InsertObject(nPos, pChild);

    MiniRect rcChild;
    pChild->GetRect(rcChild);
    DoChange(rcChild);

    return nIndex;
}

UIObject* View::GetFirstChild() const
{
    return m_pChild;
}

UIObject* View::GetLastChild() const
{
    UIObject* p = m_pChild;
    while (p && p->m_pNext)
    {
        p = p->m_pNext;
    }
    return p;
}

UIObject* View::GetNextChild(UIObject* pChild) const
{
    if (pChild && pChild->m_pParent == this)
    {
        return pChild->m_pNext;
    }
    return nullptr;
}

UIObject* View::GetPrevChild(UIObject* pChild) const
{
    if (pChild && pChild->m_pParent == this)
    {
        return pChild->m_pPrev;
    }
    return nullptr;
}

bool View::SetLayout(Layout* pLayout)
{
    if (m_pLayout != pLayout)
    {
        if (m_pLayout)
        {
            m_pLayout->Release();
        }
        if (m_pLayout)
        {
            pLayout->AddRef();
        }
        m_pLayout = pLayout;

        DoLayout();
        return true;
    }
    return false;
}

Layout* View::GetLayout() const
{
    return m_pLayout;
}

void View::DoLayout()
{
    if (m_lockLayout)
    {
        return;
    }

    LockUpdate();
    m_pLayout->DoLayout(this);
    UnlockUpdate();
}

bool View::LockLayout()
{
    if (m_lockLayout >= (uint8)(~0U))
    {
        return false;
    }
    else
    {
        m_lockLayout++;
        return true;
    }
}

bool View::UnlockLayout(bool bLayout)
{
    if (m_lockLayout)
    {
        m_lockLayout--;
        if (!m_lockLayout)
        {
            if (bLayout)
            {
                DoLayout();
            }
        }
        return true;
    }
    return false;
}

bool View::IsLockLayout()
{
    return !!m_lockLayout;
}

bool View::LockUpdate()
{
    if (!m_lockUpdate)
    {
        m_rcRender.SetRectEmpty();
    }
    if (m_lockUpdate >= (uint8)(~0U))
    {
        return false;
    }
    else
    {
        m_lockUpdate++;
        return true;
    }
}

bool View::UnlockUpdate(bool bRedraw)
{
    if (m_lockUpdate)
    {
        m_lockUpdate--;
        if (!m_lockUpdate)
        {
            if (bRedraw && !m_rcRender.IsRectEmpty())
            {
                DoChange(m_rcRender);
                m_rcRender.SetRectEmpty();
            }
        }
        return true;
    }
    return false;
}

bool View::RenderTo(Graphics& graphics, const Type::MiniRect& rcRender, const uint8 blendValue)
{
    if (!m_bVisible) return false;
    uint8 value = m_blendValue;
    value = ((uint16)value * (1 + blendValue)) >> 8;

    graphics.Save();
    UIObject::ShapeClip(graphics);
    RenderBackground(graphics, rcRender, value);
    RenderContent(graphics, rcRender, value);
    graphics.Restore();

    return true;
}

void View::RenderBackground(Graphics& graphics, const Type::MiniRect& rcRender, const uint8 blendValue)
{
    MiniARGB bgColor = m_bgColor;
    bgColor.alpha = ((uint16)blendValue * (1 + m_bgColor.alpha)) >> 8;
    if (bgColor.alpha == 0)
        return;
    graphics.SetDrawColor(bgColor);
    MiniRect drawRect;
    drawRect.SetRect(0, 0, m_size.cx, m_size.cy);
    graphics.DrawRectangle(drawRect);
}

void View::RenderContent(Graphics& graphics, const Type::MiniRect& rcRender, const uint8 blendValue)
{
    UIObject* pObj = GetFirstChild();
    while (pObj)
    {
        MiniRect rcObj;
        pObj->GetRect(rcObj);
        MiniRect rcObjClip;
        if (pObj->GetVisible() && rcObjClip.IntersectRect(rcRender, rcObj))
        {
            MiniPoint ptOrg;
            pObj->GetPos(ptOrg);
            graphics.SaveMatrix();
            graphics.Translate(ptOrg.x, ptOrg.y);   //translate org point.
            rcObjClip.OffsetRect(-ptOrg.x, -ptOrg.y);
            pObj->RenderTo(graphics, rcObjClip, blendValue);
            graphics.Restore();
        }
        pObj = pObj->m_pNext;
    }
}

void View::AddObject(UIObject* pChild)
{
    pChild->AddRef();
    View* pParent = pChild->m_pParent;
    if (pParent)
    {
        pParent->DeleteChild(pChild);
    }

    UIObject* pObj = GetLastChild();
    if (pObj)
    {
        pObj->m_pNext = pChild;
        pChild->m_pPrev = pObj;
    }
    else
    {
        m_pChild = pChild;
    }

    pChild->m_pParent = this;
    pChild->DoParentChange();
}

void View::DeleteObject(UIObject* pChild)
{
    UIObject* pPrev = pChild->m_pPrev;
    UIObject* pNext = pChild->m_pNext;
    if (pPrev)
    {
        pPrev->m_pNext = pChild->m_pNext;
    }
    else
    {
        m_pChild = pNext;
    }
    if (pNext)
    {
        pNext->m_pPrev = pPrev;
    }
    pChild->m_pPrev = nullptr;
    pChild->m_pNext = nullptr;

    pChild->DoParentChange();
    pChild->m_pParent = nullptr;

    pChild->SetWinView(nullptr);
    pChild->Release();
}

int View::InsertObject(int nPos, UIObject* pChild)
{
    if (nPos < 0)
    {
        return -1;
    }

    pChild->AddRef();
    View* pParent = pChild->m_pParent;
    if (pParent)
    {
        pParent->DeleteChild(pChild);
    }
    else
    {
        pChild->m_pNext = nullptr;
        pChild->m_pPrev = nullptr;
    }

    UIObject* pObj = m_pChild;
    int nIndex = 0;
    if (nPos > 0)
    {
        if (pObj)
        {
            nIndex = 1;
            UIObject* pNext = pObj->m_pNext;
            while (pNext && nIndex < nPos)
            {
                pObj = pNext;
                pNext = pNext->m_pNext;
                nIndex++;
            }

            if (pObj)
            {
                pObj->m_pNext = pChild;
                pChild->m_pPrev = pObj;
            }

            if (pNext)
            {
                pNext->m_pPrev = pChild;
                pChild->m_pNext = pNext;
            }
        }
        else
        {
            m_pChild = pChild;
        }
    }
    else
    {
        if (pObj)
        {
            pObj->m_pPrev = pChild;
            pChild->m_pNext = pObj;
        }
        m_pChild = pChild;
    }

    pChild->m_pParent = this;

    pChild->DoParentChange();
    if (pChild->CheckType(View::GetTypeName()))
    {
        static_cast<View*>(pChild)->DoLayout();
    }

    return nIndex;
}

void View::GetMousePosDifferToChildPos(const MiniPoint& pt, UIObject* pObj, MiniPoint& ptChild)
{
    ptChild.SetPoint(0, 0);
    MiniPoint ptObj;
    pObj->GetPos(ptObj);
    ptChild += pt - ptObj;
}

UIObject* View::ChildHitTest(const MiniPoint& pt)
{
    UIObject* pObj = GetLastChild();
    while (pObj)
    {
        if (pObj->CheckType(View::GetTypeName()))
        {
            //View
            if (pObj->HitTest(pt))
            {
                if (!pObj->m_bTransparent)
                {
                    return pObj;
                }
                else
                {
                    MiniPoint ptChild;
                    GetMousePosDifferToChildPos(pt, pObj, ptChild);
                    if (static_cast<View*>(pObj)->DoHitTest(ptChild))
                    {
                        return pObj;
                    }
                }
            }
            pObj =pObj->m_pPrev;
        }
        else
        {
            //UIObject
            if (pObj->HitTest(pt) && !pObj->m_bTransparent)
            {
                return pObj;
            }
            pObj = pObj->m_pPrev;
        }
    }
    return nullptr;
}

UIObject* View::DoHitTest(const Type::MiniPoint& pt)
{
    UIObject* pObj = ChildHitTest(pt);
    if (pObj && pObj->CheckType(View::GetTypeName()))
    {
        MiniPoint ptChild;
        GetMousePosDifferToChildPos(pt, pObj, ptChild);
        UIObject* pObjChild = static_cast<View*>(pObj)->DoHitTest(ptChild);
        if (pObjChild)
        {
            pObj = pObjChild;
        }
    }
    if (pObj && pObj->m_bTransparent)
    {
        pObj = nullptr;
    }
    return pObj;
}

void View::ChildChange(const UIObject* pChild, const MiniRect& rect)
{
    MiniRect rc = rect;
    MiniPoint pt;
    pChild->GetPos(pt);
    rc += pt;
    MiniRect rcView;
    rcView.SetRect(0, 0, m_size.cx, m_size.cy);
    rc.IntersectRect(rc, rcView);
    if (m_lockUpdate)
    {
        m_rcRender.UnionRect(m_rcRender, rc);
    }
    else
    {
        DoChange(rc);
    }
}

void View::ChildMove(const UIObject* pChild, const MiniPoint& ptSrc, const MiniPoint& ptDst)
{
    MiniSize size;
    pChild->GetSize(size);
    MiniRect rcSrc;
    rcSrc.SetRect(ptSrc, ptSrc + size);
    MiniRect rcDst;
    rcDst.SetRect(ptDst, ptDst + size);
    MiniRect rc;
    rc.UnionRect(rcSrc, rcDst);
    MiniRect rcView;
    rcView.SetRect(0, 0, m_size.cx, m_size.cy);
    rc.IntersectRect(rc, rcView);
    if (m_lockUpdate)
    {
        m_rcRender.UnionRect(m_rcRender, rc);
    }
    else
    {
        DoChange(rc);
    }
}

void View::ChildResize(const UIObject* pChild, const MiniSize& szOld, const MiniSize& szNew)
{
    MiniPoint pt;
    pChild->GetPos(pt);
    MiniRect rcOld;
    rcOld.SetRect(pt, pt + szOld);
    MiniRect rcNew;
    rcNew.SetRect(pt, pt + szNew);
    MiniRect rc;
    rc.UnionRect(rcOld, rcNew);
    MiniRect rcView;
    rcView.SetRect(0, 0, m_size.cx, m_size.cy);
    rc.IntersectRect(rc, rcView);
    if (m_lockUpdate)
    {
        m_rcRender.UnionRect(m_rcRender, rc);
    }
    else
    {
        DoChange(rc);
    }
}

bool View::Click(const Type::MiniPoint& pt, MouseButton button, uint keyState, bool& bHandled)
{
    bHandled = true;
    UIObject* pObj = ChildHitTest(pt);
    if (pObj)
    {
        MiniPoint ptChild;
        GetMousePosDifferToChildPos(pt, pObj, ptChild);
        bool bChildHandle;
        pObj->DoClick(ptChild, button, keyState, bChildHandle);
        return bChildHandle;
    }
    return false;
}

bool View::DblClk(const Type::MiniPoint& pt, MouseButton button, uint keyState, bool& bHandled)
{
    bHandled = true;
    UIObject* pObj = ChildHitTest(pt);
    if (pObj)
    {
        MiniPoint ptChild;
        GetMousePosDifferToChildPos(pt, pObj, ptChild);
        bool bChildHandle;
        pObj->DoDblClk(ptChild, button, keyState, bChildHandle);
        return bChildHandle;
    }
    return false;
}

bool View::MouseDown(const Type::MiniPoint& pt, MouseButton button, uint keyState, bool& bHandled)
{
    bHandled = false;
    UIObject* pObj = ChildHitTest(pt);
    m_pObjMouseDown = pObj;
    if (pObj)
    {
        MiniPoint ptChild;
        GetMousePosDifferToChildPos(pt, pObj, ptChild);
        pObj->DoMouseDown(ptChild, button, keyState, bHandled);
        return bHandled;
    }
    return false;
}

bool View::MouseUp(const Type::MiniPoint& pt, MouseButton button, uint keyState, bool& bHandled)
{
    bHandled = true;
    if (m_pObjMouseEnter && !m_pObjMouseEnter->HitTest(pt))
    {
        if (!((keyState & MB_L) || (keyState & MB_R) || (keyState & MB_M)) || (keyState & MB_X1) || (keyState & MB_X2))
        {
            m_pObjMouseEnter->DoMouseLeave();
            m_pObjMouseEnter = nullptr;
        }
    }
    if (m_pObjMouseDown)
    {
        MiniPoint ptChild;
        GetMousePosDifferToChildPos(pt, m_pObjMouseDown, ptChild);
        bool bChildHandle;
        m_pObjMouseDown->DoMouseUp(ptChild, button, keyState, bChildHandle);
        if (!((keyState & MB_L) || (keyState & MB_R) || (keyState & MB_M)) || (keyState & MB_X1) || (keyState & MB_X2))
        {
            m_pObjMouseDown = nullptr;
        }
        return bChildHandle;
    }
    return false;
}

bool View::MouseMove(const MiniPoint& pt, uint keyState, bool& bHandled)
{
    bHandled = true;
    UIObject* pObj = ChildHitTest(pt);
    if (m_pObjMouseDown)
    {
        MiniPoint ptChild;
        GetMousePosDifferToChildPos(pt, m_pObjMouseDown, ptChild);
        bool bChildHandle;
        m_pObjMouseDown->DoMouseMove(ptChild, keyState, bChildHandle);
        if (m_pObjMouseDown == pObj)
        {
            if (m_pObjMouseEnter != pObj)
            {
                if (m_pObjMouseEnter)
                {
                    m_pObjMouseEnter->DoMouseLeave();
                    m_pObjMouseEnter = nullptr;
                }
                pObj->DoMouseEnter();
                m_pObjMouseEnter = pObj;
            }
        }
        else
        {
            if (m_pObjMouseEnter)
            {
                m_pObjMouseEnter->DoMouseLeave();
                m_pObjMouseEnter = nullptr;
            }
        }
    }
    else
    {
        if (pObj)
        {
            MiniPoint ptChild;
            GetMousePosDifferToChildPos(pt, pObj, ptChild);
            bool bChildHandle;
            if (m_pObjMouseEnter != pObj)
            {
                if (m_pObjMouseEnter)
                {
                    m_pObjMouseEnter->DoMouseLeave();
                    m_pObjMouseEnter = nullptr;
                }
                pObj->DoMouseEnter();
                m_pObjMouseEnter = pObj;
            }
            pObj->DoMouseMove(ptChild, keyState, bChildHandle);
            return bChildHandle;
        }
        else
        {
            if (m_pObjMouseEnter)
            {
                m_pObjMouseEnter->DoMouseLeave();
                m_pObjMouseEnter = nullptr;
            }
        }
    }

    if (!pObj)
    {
        if (m_pWinView)
        {
            m_pWinView->SetCursor(m_cursor, m_customCursor);
        }
    }

    return false;
}

void View::MouseHover()
{
}

void View::MouseEnter()
{
}

void View::MouseLeave()
{
    if (m_pObjMouseEnter)
    {
        m_pObjMouseEnter->DoMouseLeave();
        m_pObjMouseEnter = nullptr;
    }
}

bool View::MouseWheel(const Type::MiniPoint& pt, uint keyState, int16 zDelta, bool& bHandled)
{
    bHandled = true;
    UIObject* pObj = ChildHitTest(pt);
    if (pObj)
    {
        MiniPoint ptChild;
        GetMousePosDifferToChildPos(pt, pObj, ptChild);
        bool bChildHandle;
        pObj->DoMouseWheel(ptChild, keyState, zDelta, bChildHandle);
        return bChildHandle;
    }
    return false;
}

//void View::Change(const MiniRect& rect)
//{
//    NotifyParentChange(rect);
//}
//
//void View::Move(const MiniPoint& ptSrc, const MiniPoint& ptDst)
//{
//    NotifyParentMove(ptSrc, ptDst);
//}

void View::Resize(const MiniSize& szOld, const MiniSize& szNew)
{
    LockUpdate();
    DoLayout();
    UnlockUpdate();
    NotifyParentResize(szOld, szNew);
}

void View::Init()
{
    m_pChild = nullptr;
    m_pObjMouseDown = nullptr;
    m_pObjMouseEnter = nullptr;
    m_lockLayout = 0;
    m_lockUpdate = 0;
    m_pLayout = NormalLayout::CreateInstance();
}